<template>
	<el-upload
		class="avatar-uploader"
		list-type="picture-card"
		:auto-upload="false"
		:file-list="fileList"
		:on-change="handleChange"
		:on-remove="handleRemove"
		:limit="1"
		accept="image/*"
	>
		<el-icon class="avatar-uploader-icon">
			<Plus />
		</el-icon>
	</el-upload>
</template>

<script setup lang="ts">
import { ref, watch, onMounted } from 'vue';
import { Plus } from '@element-plus/icons-vue';

const props = defineProps<{ modelValue: string }>();
const emit = defineEmits(['update:modelValue']);

const fileList = ref<any[]>([]);

/** 判断是否是 base64 */
const isBase64 = (val: string) => typeof val === 'string' && val.startsWith('data:image/');

/** 初始化 base64 显示 */
const initFromBase64 = (base64: string) => {
	if (isBase64(base64)) {
		fileList.value = [
			{
				name: '已上传',
				url: base64,
				status: 'finished',
			},
		];
	} else {
		fileList.value = [];
	}
};

/** 压缩图片（最大宽高限制 + 质量控制） */
const compressImage = (file: File, maxSizeKB = 100): Promise<string> => {
	return new Promise((resolve) => {
		const reader = new FileReader();
		reader.onload = (e) => {
			const img = new Image();
			img.onload = () => {
				const canvas = document.createElement('canvas');
				const ctx = canvas.getContext('2d')!;
				let [w, h] = [img.width, img.height];

				// 限制最大宽高（推荐值）
				const maxW = 1024;
				const maxH = 1024;
				if (w > maxW || h > maxH) {
					const scale = Math.min(maxW / w, maxH / h);
					w = w * scale;
					h = h * scale;
				}
				canvas.width = w;
				canvas.height = h;
				ctx.drawImage(img, 0, 0, w, h);

				let quality = 0.8;
				let base64 = canvas.toDataURL('image/jpeg', quality);

				// 递减压缩直到小于 maxSizeKB 或最低质量
				while (base64.length / 1024 > maxSizeKB && quality > 0.3) {
					quality -= 0.1;
					base64 = canvas.toDataURL('image/jpeg', quality);
				}

				resolve(base64);
			};
			img.src = e.target?.result as string;
		};
		reader.readAsDataURL(file);
	});
};

/** 将文件转成 base64 */
const toBase64 = (file: File): Promise<string> =>
	new Promise((resolve) => {
		const reader = new FileReader();
		reader.onload = () => resolve(reader.result as string);
		reader.readAsDataURL(file);
	});

/** 处理上传逻辑 */
const handleChange = async (file: any) => {
	const rawFile: File = file.raw;
	const sizeKB = rawFile.size / 1024;

	let base64 = '';
	if (sizeKB > 100) {
		base64 = await compressImage(rawFile, 100);
	} else {
		base64 = await toBase64(rawFile);
	}

	emit('update:modelValue', base64);
	initFromBase64(base64);
};

/** 删除图片 */
const handleRemove = () => {
	emit('update:modelValue', '');
	fileList.value = [];
};

/** 初始化加载 base64 */
onMounted(() => {
	initFromBase64(props.modelValue);
});

/** 父组件数据变化监听 */
watch(
	() => props.modelValue,
	(val) => {
		initFromBase64(val);
	}
);
</script>

<style scoped>
.avatar-uploader .el-upload {
	border: 1px dashed #d9d9d9;
	border-radius: 6px;
	cursor: pointer;
	position: relative;
	overflow: hidden;
}
.avatar-uploader .el-upload:hover {
	border-color: #409eff;
}
.avatar-uploader-icon {
	font-size: 28px;
	color: #8c939d;
	width: 178px;
	height: 178px;
	line-height: 178px;
	text-align: center;
}
</style>
